#include "LogCollectionPrivate.hpp"

#include <chrono>
#include <ctime>
#include <dirent.h>
#include <fstream>
#include <filesystem>
#include <istream>
#include <sys/stat.h>
#include <systemd/sd-journal.h>

namespace LogCollector
{

using namespace std::chrono_literals;

namespace fs = std::filesystem;

//! /proc/pid/stat 第22个字段为进程启动时间(单位：时钟滴答)
static constexpr int START_TIME_OFFSET { 22 };

//! 队列等待事件处理的超时时间
static constexpr std::chrono::milliseconds QUEUE_WAIT_TIME_OUT { 100ms };

std::string __getCurrentTimestamp()
{
    using namespace std::chrono;

    //! 获取时间戳
    auto now = system_clock::now();
    std::time_t rawTime = system_clock::to_time_t(now);

    //! 转换为本地时间
    std::tm localTime;
    localtime_r(&rawTime, &localTime);

    //! 计算毫秒部分
    auto msec = duration_cast<milliseconds>(now.time_since_epoch()) % TIME_CONVERSION_BASE;

    return LogCollector::format(
        "{}-{}-{L 2 0} {L 2 0}:{L 2 0}:{L 2 0}.{L 3 0}",
        localTime.tm_year + TIME_YEAR_OFFSET,
        localTime.tm_mon + TIME_MONTH_OFFSET,
        localTime.tm_mday,
        localTime.tm_hour,
        localTime.tm_min,
        localTime.tm_sec,
        msec.count()
    );
}

pid_t __getSelfThreadId()
{
    return gettid();
}

pid_t __getProcessIdByName(const std::string& aProcessName)
{
    DIR* dir = opendir("/proc");
    if (!dir) {
        return -1;
    }

    struct dirent* entry;
    while ((entry = readdir(dir)) != nullptr) {
        if (entry->d_type != DT_DIR || !std::isdigit(entry->d_name[0])) {
            continue;
        }
        pid_t pid = std::stoi(entry->d_name);
        std::string path = "/proc/" + std::to_string(pid) + "/cmdline";
        std::ifstream cmdlineFile(path);
        if (!cmdlineFile.is_open()) {
            continue;
        }
        std::string cmdline;
        std::getline(cmdlineFile, cmdline);
        cmdlineFile.close();
        auto pos = cmdline.find_last_of("/");
        if (pos == std::string::npos) {
            continue;
        }
        cmdline = cmdline.substr(pos + 1, cmdline.length() - pos - 2);
        if (cmdline == aProcessName) {
            closedir(dir);
            return pid;
        }
    }

    closedir(dir);

    return -1;
}

bool __getSelfProcessName(char* aBuffer, size_t aSize)
{
    auto len = readlink("/proc/self/exe", aBuffer, aSize - 1);
    if (len == -1) {
        return false;
    }

    aBuffer[len] = '\0';
    strncpy(aBuffer, strrchr(aBuffer, '/') + 1, aSize);

    return true;
}

uint64_t __getProcessStartTime(pid_t aPid)
{
    std::string statPath = "/proc/" + std::to_string(aPid) + "/stat";
    std::ifstream statFile(statPath);
    if (!statFile.is_open()) {
        return -1;
    }

    std::string line;
    std::getline(statFile, line);
    statFile.close();

    std::istringstream iss(line);
    std::string token;
    for (size_t i = 0; i < START_TIME_OFFSET; ++i) {
        iss >> token;
    }
    return std::stoll(token);
}

Logger* __createDefaultLogger(const char* aProcessName)
{
    char* processName = new char[SINGLE_BUFFER_SIZE];
    if (aProcessName) {
        strncpy(processName, aProcessName, SINGLE_BUFFER_SIZE);
    } else {
        if (!__getSelfProcessName(processName, SINGLE_BUFFER_SIZE)) {
            return nullptr;
        }
    }

    return new Logger {
        .mFd { -1 },
        .mBus { nullptr },
        .mEvent { nullptr },
        .mProcessName { processName },
        .mFromLocal { true },
        .mIsWriteToJournal { false },
        .mLevel { static_cast<int>(Severity::Level::DEBUG) },
        .mMaxFileSize { DEFAULT_MAX_FILE_SIZE },
        .mMaxFileCount { DEFAULT_MAX_FILE_COUNT },
        .mMaxFlushSize { DEFAULT_MAX_FLUSH_SIZE },
        .mMaxFlushTimeOut { DEFAULT_MAX_FLUSH_TIMEOUT },
        .mQueue {QUEUE_WAIT_TIME_OUT}
    };
}

ReturnStatus __initLogFile(Logger* aLogger)
{
    if (!aLogger) {
        return ReturnStatus::NOT_INIT;
    }

    auto filePath = LogCollector::format(LOG_FILE_BARE_FMT, aLogger->mProcessName);

    mkdir(LOG_DIRECTORY.c_str(), 0755);

    //! 使用 c++ 提供的 filesystem 检查文件是否存在，代替 POSIX 的 access 和 stat
    if (fs::exists(filePath) && fs::file_size(filePath) > 0) {
        __rotateLog(aLogger);
    } else {
        aLogger->mFd = open(filePath.c_str(), O_RDWR | O_TRUNC | O_CREAT, 0644);
        if (aLogger->mFd == -1) {
            return ReturnStatus::FILE_ERROR;
        }
    }

    return ReturnStatus::SUCCESS;
}

LogConfigure __createDefaultConfig(const char* aProcessName)
{
    return LogConfigure {
        .mProcessName = aProcessName,
        .mFromLocal = true,
        .mIsWriteToJournal = false,
        .mSeverityLevel = static_cast<int>(Severity::Level::DEBUG),
        .mMaxFlushTimeOut = DEFAULT_MAX_FLUSH_TIMEOUT,
        .mMaxFlushSize = DEFAULT_MAX_FLUSH_SIZE,
        .mMaxFileCount = DEFAULT_MAX_FILE_COUNT,
        .mMaxFileSize = DEFAULT_MAX_FILE_SIZE
    };
}

void __switchConfigure(LogConfigure& config, const std::string& key, const std::string& value)
{
    if (key == "Severity") {
        config.mSeverityLevel = static_cast<int>(Severity::fromConfigString(value));
    } else if (key == "MaxFlushTimeout") {
        config.mMaxFlushTimeOut = static_cast<uint16_t>(std::stoi(value));
    } else if (key == "MaxFlushSize") {
        config.mMaxFlushSize = static_cast<uint8_t>(std::stoi(value));
    } else if (key == "MaxFileCount") {
        config.mMaxFileCount = static_cast<uint8_t>(std::stoi(value));
    } else if (key == "MaxFileSize") {
        config.mMaxFileSize = static_cast<uint32_t>(std::stoi(value));
    } else if (key == "From") {
        if (value == "Journal") {
            config.mFromLocal = false;
        } else if (value == "Local") {
            config.mFromLocal = true;
        }
    } else if (key == "IsWriteToJournal") {
        if (value == "true") {
            config.mIsWriteToJournal = true;
        } else {
            config.mIsWriteToJournal = false;
        }
    } else if (key == "ExecPath") {
        config.mExecPath = value;
    }
}

std::vector<LogConfigure> __loadLogConfigureAll()
{
    std::vector<LogConfigure> configs;
    //! 使用c++标准库读取配置文件
    std::ifstream configFile("/etc/log-collector/LogConfigure.conf");
    if (!configFile) {
        return configs;
    }

    std::string line;
    LogConfigure config = __createDefaultConfig("");
    while (std::getline(configFile, line)) {
        //! 去除行首尾空白字符
        line.erase(0, line.find_first_not_of(" \r\n\t"));
        line.erase(line.find_last_not_of(" \r\n\t") + 1);
        
        //! 跳过注释和空行
        if (line.empty() || line[0] == '#') {
            continue;
        }

        //! 首先找到 '[' ']' 包围的进程名
        if (line.front() == '[' && line.back() == ']') {
            if (!config.mProcessName.empty()) {
                configs.push_back(config);
                config = __createDefaultConfig("");
            }
            config.mProcessName = line.substr(1, line.size() - 2);
            continue;
        }

        //! 处理匹配到的进程名配置项
        if (!config.mProcessName.empty()) {
            auto delimiterPos = line.find('=');
            if (delimiterPos == std::string::npos) {    
                continue;
            }

            std::string key = line.substr(0, delimiterPos);
            std::string value = line.substr(delimiterPos + 1);

            __switchConfigure(config, key, value);
        }
    }

    configs.push_back(config);

    return configs;
}

LogConfigure __loadLogConfigureOne(const char* aProcessName)
{
    auto configs = __loadLogConfigureAll();
    for (const auto config : configs) {
        if (config.mProcessName == aProcessName) {
            return config;
        }
    }
    return __createDefaultConfig(aProcessName);
}

std::string __combineLogStringVargs(Logger* aLogger, Severity::Level aLevel, pid_t aTid,
    const char* aTimeStamp, const char* aFile, int aLine, const char* aFunc, const char* aFmt, va_list args)
{
    //! 处理 level
    const char* levelString;
    switch (aLevel)
    {
    case Severity::Level::DEBUG:
        levelString = "DBG";
        break;
    case Severity::Level::INFO:
        levelString = "INF";
        break;
    case Severity::Level::WARNING:
        levelString = "WRN";
        break;
    case Severity::Level::ERROR:
        levelString = "ERR";
        break;
    case Severity::Level::CRITICAL:
        levelString = "CRT";
        break;
    default:
        levelString = "UNK";
    }
    //! 处理file
    aFile = strrchr(aFile, '/') != nullptr ? strrchr(aFile, '/') + 1 : aFile;

    //! 拼接字符串
    char output[SINGLE_BUFFER_SIZE] { 0 };
    int len = snprintf(output, SINGLE_BUFFER_SIZE - 1, "[%s][%d][%s][%s:%d %s] ",
       aTimeStamp, aTid, levelString, aFile, aLine, aFunc);
    len = vsnprintf(output + len, SINGLE_BUFFER_SIZE - len - 1, aFmt, args);
    if (len < 0) {
        return "";
    }

    len = sizeof(output);
    std::string msg(output, len);
    msg.push_back('\n');

    return msg;
}

ReturnStatus __processCombinedLog(Logger* aLogger, const std::string& msg)
{
    auto len = msg.size();
    if (aLogger->mCurrentBufferSize + len <= BUFFER_SIZE) {
        memcpy(aLogger->mWriteBuffer + aLogger->mCurrentBufferSize, msg.c_str(), len);
        aLogger->mCurrentBufferSize += len;
        return ReturnStatus::SUCCESS;
    }

    return __flushLog(aLogger, msg.c_str(), len);
}

ReturnStatus __writeLogSync(Logger* aLogger, Severity::Level aLevel, pid_t aTid,
    const char* aTimeStamp, const char* aFile, int aLine, const char* aFunc, const char* aFmt, ...)
{
    va_list args;
    va_start(args, aFmt);
    auto ret = __writeLogVargsSync(aLogger, aLevel, aTid, aTimeStamp, aFile, aLine, aFunc, aFmt, args);
    va_end(args);
    return ret;
}

ReturnStatus __writeLogVargsSync(Logger* aLogger, Severity::Level aLevel, pid_t aTid,
    const char* aTimeStamp, const char* aFile, int aLine, const char* aFunc, const char* aFmt, va_list aArgs)
{    
    std::string msg = __combineLogStringVargs(aLogger, aLevel, aTid, aTimeStamp, aFile, aLine, aFunc, aFmt, aArgs);

    if (msg.empty()) {
        return ReturnStatus::UNKNOWN_ERROR;
    }

    return __processCombinedLog(aLogger, msg);
}

ReturnStatus __writeLogAsync(Logger* aLogger, Severity::Level aLevel, pid_t aTid,
    const char* aTimeStamp, const char* aFile, int aLine, const char* aFunc, const char* aFmt, ...)
{
    va_list args;
    va_start(args, aFmt);
    auto ret = __writeLogVargsAsync(aLogger, aLevel, aTid, aTimeStamp, aFile, aLine, aFunc, aFmt, args);
    va_end(args);
    return ret;
}

ReturnStatus __writeLogVargsAsync(Logger* aLogger, Severity::Level aLevel, pid_t aTid,
    const char* aTimeStamp, const char* aFile, int aLine, const char* aFunc, const char* aFmt, va_list aArgs)
{
    std::string msg = __combineLogStringVargs(aLogger, aLevel, aTid, aTimeStamp, aFile, aLine, aFunc, aFmt, aArgs);
    if (msg.empty()) {
        return ReturnStatus::UNKNOWN_ERROR;
    }

    aLogger->mQueue.enqueue(
        StoreLogTask{
            &__processCombinedLog,
            msg
        }
    );

    return ReturnStatus::SUCCESS;
}

ReturnStatus __rotateLog(Logger* aLogger)
{
    if (!aLogger) {
        return ReturnStatus::NOT_INIT;
    }

    int curCount = 1;
    while (curCount < aLogger->mMaxFileCount) {
        auto filePath = LogCollector::format(LOG_FILE_SUBSCRIPT_FMT, aLogger->mProcessName, curCount);

        //! 检查文件是否存在
        if (access(filePath.c_str(), F_OK) != F_OK) {
            break;
        }

        ++curCount;
    }

    std::string oldPath;
    std::string newPath;
    for (int i = curCount; i > 0; --i) {
        if (i == 1) {
            oldPath = LogCollector::format(
                LOG_FILE_BARE_FMT, aLogger->mProcessName);
        } else {
            oldPath = LogCollector::format(
                LOG_FILE_SUBSCRIPT_FMT, aLogger->mProcessName, i - 1);
        }

        newPath = LogCollector::format(
            LOG_FILE_SUBSCRIPT_FMT, aLogger->mProcessName, i);
        if (rename(oldPath.c_str(), newPath.c_str()) == -1) {
            //! rename error
            return ReturnStatus::FILE_ERROR;
        }
    }

    if (aLogger->mFd != -1 && close(aLogger->mFd) == -1) {
        return ReturnStatus::FILE_ERROR;
    }

    //! == 的优先级比 = 高
    if (aLogger->mFd = open(oldPath.c_str(), O_CREAT | O_RDWR | O_TRUNC, 0644); aLogger->mFd == -1) {
        return ReturnStatus::FILE_ERROR;
    }

    return ReturnStatus::SUCCESS;
}

ReturnStatus __flushLog(Logger* aLogger, const char* aOutput, int aLen)
{
    //! 截断文件，并创建新文件
    if (aLogger->mCurrentFileSize + BUFFER_SIZE > aLogger->mMaxFileSize) {
        const auto ret = __rotateLog(aLogger);
        if (ret != ReturnStatus::SUCCESS) {
            return ret;
        }
        aLogger->mCurrentFileSize = 0;
    }

    //! 写文件
    if (write(aLogger->mFd, aLogger->mWriteBuffer, aLogger->mCurrentBufferSize) == -1) {
        return ReturnStatus::FILE_ERROR;
    }

    aLogger->mCurrentFileSize += aLogger->mCurrentBufferSize;
    aLogger->mCurrentBufferSize = aLen;
    if (aOutput) {
        memcpy(aLogger->mWriteBuffer, aOutput, aLen);
    } else {
        memset(aLogger->mWriteBuffer, 0, BUFFER_SIZE);
    }

    return ReturnStatus::SUCCESS;
}

ReturnStatus __clearLogFile(Logger* aLogger)
{
    if (close(aLogger->mFd) == -1) {
        return ReturnStatus::FILE_ERROR;
    }

    int curCount = 1;
    std::string filePath;
    while (curCount <= aLogger->mMaxFileCount) {
        filePath = LogCollector::format(LOG_FILE_SUBSCRIPT_FMT, aLogger->mProcessName, curCount);

        //! 检查文件是否存在
        if (access(filePath.c_str(), F_OK) != F_OK) {
            break;
        }

        //! 删除文件
        if (unlink(filePath.c_str()) == -1) {
            return ReturnStatus::FILE_ERROR;
        }

        ++curCount;
    }

    //! == 的优先级比 = 高
    filePath = LogCollector::format(LOG_FILE_BARE_FMT, aLogger->mProcessName);
    
    if ((aLogger->mFd = open(filePath.c_str(), O_CREAT | O_RDWR | O_TRUNC, 0644)) == -1) {
        return ReturnStatus::FILE_ERROR;
    }

    return ReturnStatus::SUCCESS;
}

ReturnStatus __writeJournalVargs(Severity::Level aLevel,
    const char* aFile, int aLine, const char* aFunc, const char* aFmt, va_list aArgs)
{
    //! 拼接 MESSAGE 字段
    char msg[SINGLE_BUFFER_SIZE];
    vsnprintf(msg, sizeof(msg), aFmt, aArgs);

    //! 发送日志到 journal
    auto ret = sd_journal_send(
        "MESSAGE=%s", msg,
        "PRIORITY=%i", Severity::toJournalLevel(aLevel),
        "CODE_FILE=%s", aFile,
        "CODE_LINE=%i", aLine,
        "CODE_FUNC=%s", aFunc,
        nullptr
    );

    return (ret >= 0) ? ReturnStatus::SUCCESS : ReturnStatus::UNKNOWN_ERROR;
}

std::vector<std::string> __split(const std::string& aStr, char aBreakPoint)
{
    std::vector<std::string> splited;

    size_t breakPos = 0;
    for (size_t pos = 0; pos < aStr.size(); ++pos) {
        if (aStr[pos] != aBreakPoint) {
            continue;
        }

        splited.push_back(aStr.substr(breakPos, pos - breakPos));

        breakPos = pos + 1;
    }

    splited.push_back(aStr.substr(breakPos, aStr.size() - breakPos));
    return splited;
}

}